package version2;

import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Frame;
import java.awt.GraphicsConfiguration;
import java.awt.GridLayout;
import java.awt.TextArea;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.media.j3d.*;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;
import javax.vecmath.Vector4d;

import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.geometry.ColorCube;
import com.sun.j3d.utils.geometry.GeometryInfo;
import com.sun.j3d.utils.geometry.NormalGenerator;
import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.image.TextureLoader;

import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.Viewer;
import com.sun.j3d.utils.universe.ViewingPlatform;


@SuppressWarnings({ "serial" })
public class Reconstruction extends Applet implements ActionListener, MouseWheelListener
{
	static ArrayList<Slice> slices;
	static ArrayList<Projection> projections;
	static ArrayList<MitoData> mitoData;
	static ArrayList<Vesicle> docked;
	static ArrayList<Vesicle> tethered;
	static ArrayList<Vesicle> sv;
	static ArrayList<Vesicle> cores;
	static ArrayList<Vesicle> dockedCores;
	static ArrayList<Vesicle> larges;
	static ArrayList<Dense> densePoints;
	static ArrayList<Membrane> membranePoints;
	static ArrayList<Mitochondria> mitochondriaPoints;
	static ArrayList<Point3f> tPoints;
	static Point3f[] points;
	static double maxZ;
	static double minZ;
	static int total = 0;
	static float sliceWidth = 0.0f;  // User input width of slices
	static float magnification = 0.0f;
	static int sliceNumber = 0;  // Increment this number after parse is called
	static int firstSliceNum;
	static int selectedSlice;
	static File[] files;
	static JFrame gui;
	static JRadioButton all;
	static JRadioButton slice;
	static JButton button;
	static JCheckBox dock;
	static JCheckBox tether;
	static JCheckBox norm;
	static JCheckBox lv;
	static JCheckBox dcv;
	static JCheckBox ddcv;
	static JCheckBox dp;
	static JCheckBox mito;
	static JCheckBox memb;
	static JComboBox sliceBox;
    static TextArea ta;
	static BranchGroup scene;
	static SimpleUniverse simpleU;
	static ViewPlatform vp;
	static View v;
	static OrbitBehavior orbit;
	static float offsetX;
	static float offsetY;
	static float offsetZ;
	static float vesicleCount;
	
	public Reconstruction()
	{
		setLayout(new BorderLayout());
		GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
		Canvas3D canvas3D = new Canvas3D(config);
		add("Center", canvas3D);
		
		ViewingPlatform viewPlat = new ViewingPlatform();
		vp = new ViewPlatform();
		vp.setCapability(ViewPlatform.ALLOW_LOCAL_TO_VWORLD_READ);
		vp.setCapability(ViewPlatform.ALLOW_POLICY_READ);
	    vp.setCapability(ViewPlatform.ALLOW_POLICY_WRITE);
	  	viewPlat.setNominalViewingTransform(); // Set to look at thing here
		viewPlat.setViewPlatform(vp);
		Viewer viewer = new Viewer(canvas3D);
		v = viewer.getView();
		
		// SimpleUniverse is a Convenience Utility class
		simpleU = new SimpleUniverse(viewPlat, viewer);
		
		// This moves the ViewPlatform back a bit so the objects in the scene can be viewed.
		
		scene = createSceneGraph(false, false, false, false, false, false, false, false, false, false);
		scene.setCapability(BranchGroup.ALLOW_DETACH);
		scene.compile();
		
		// Set up mouse rotation
		orbit = new OrbitBehavior(canvas3D, OrbitBehavior.REVERSE_ALL + OrbitBehavior.PROPORTIONAL_ZOOM);
		BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
		        50000.0);
		orbit.setSchedulingBounds(bounds);
		orbit.setTransXFactor(100);
		orbit.setTransYFactor(100);
		
		ViewingPlatform viewingPlatform = simpleU.getViewingPlatform();
		viewingPlatform.setViewPlatformBehavior(orbit);
		vp = viewingPlatform.getViewPlatform();
		
		View v = simpleU.getViewer().getView();
		v.setBackClipDistance(5000);
		v.setFrontClipDistance(0.1);
		
		simpleU.addBranchGraph(scene);
		simpleU.addBranchGraph(createBackground());
	}
	
	public static void main(String[] args)
	{
		Reconstruction r = new Reconstruction();
		
		// Read in the data
		r.setUp();
		for (File f : files)
		{
			parse(f);
			sliceNumber+= 1;
		}
		// Set the size of the default window and attach mouse wheel listener
		Frame frame = new MainFrame(r, 500, 500);
		frame.addMouseWheelListener(r);
		
		gui.setVisible(true);
		
		// Center all the points around the origin
		r.centerPoints();
		
		// Go through the dense coordinates and pair up ones belonging to the same projection
		if (densePoints.size() != 0)
			r.separateDense();
		
		// Create each dense projection
		for(Projection p : projections)
		{
			r.createProjection(p);
		}
		
		// Go through the mitochondria and pair up ones belonging to the same
		if (mitochondriaPoints.size() != 0)
			r.separateMito();
		
		// Create each dense projection
		for(MitoData md : mitoData)
		{
			r.createMito(md);
		}

		// Create the membrane
		r.createMembrane();
	}
	
	public void setUp()
	{
		slices = new ArrayList<Slice>();
		projections = new ArrayList<Projection>();
		mitoData = new ArrayList<MitoData>();
		docked = new ArrayList<Vesicle>();
		tethered = new ArrayList<Vesicle>();
		sv = new ArrayList<Vesicle>();		
		cores = new ArrayList<Vesicle>();
		dockedCores = new ArrayList<Vesicle>();
		larges = new ArrayList<Vesicle>();
		densePoints = new ArrayList<Dense>();
		membranePoints = new ArrayList<Membrane>();
		mitochondriaPoints = new ArrayList<Mitochondria>();
		tPoints = new ArrayList<Point3f>();
		offsetX = 0;
		offsetY = 0;
		offsetZ = 0;
		vesicleCount = 0;

		String s = JOptionPane.showInputDialog(null, "Please enter the width of the slices: ", 
				"Slice Width?", 1);
		sliceWidth = Float.valueOf(s.trim()).floatValue();
		
		s = JOptionPane.showInputDialog(null, "Please enter the magnification of the slices: ",
				"Magnification?", 1);
		magnification = Float.valueOf(s.trim()).floatValue();
		
		JFileChooser fc = new JFileChooser();
		fc.setMultiSelectionEnabled(true);
		fc.showOpenDialog(null);
		files = fc.getSelectedFiles();
		
		// Extract the number of the first slice being used
		String str = files[0].toString();
		Pattern intsOnly = Pattern.compile("\\d+");
		Matcher makeMatch = intsOnly.matcher(str);
		makeMatch.find();
		str = makeMatch.group();
		firstSliceNum = Integer.parseInt(str);
		
		// Set up the GUI
		gui = new JFrame("Select components to draw.");
		gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		all = new JRadioButton("All Data");
		slice = new JRadioButton("Single Slice");
		all.setSelected(true);
		ButtonGroup group = new ButtonGroup();
		group.add(all);
		group.add(slice);
		JPanel selection = new JPanel(new GridLayout(1, 0));
		selection.add(all);
		selection.add(slice);
		dock = new JCheckBox("Docked Vesicles", true);
		tether = new JCheckBox("Tethered Vesicles", true);
		norm = new JCheckBox("Synaptic Vesicles", true);
		lv = new JCheckBox("Large Vesicles", true);
		dcv = new JCheckBox("Dense Core Vesicles", true);
		ddcv = new JCheckBox("Docked Dense Core Vesicles", true);
		dp = new JCheckBox("Dense Projections", true);
		mito = new JCheckBox("Mitochondria", true);
		memb = new JCheckBox("Membrane", true);
		JPanel checkPanel = new JPanel(new GridLayout(0, 1));
        checkPanel.add(dock);
        checkPanel.add(tether);
        checkPanel.add(norm);
        checkPanel.add(lv);
        checkPanel.add(dcv);
        checkPanel.add(ddcv);
        checkPanel.add(dp);
        checkPanel.add(mito);
        checkPanel.add(memb);
        
        gui.getContentPane().add(checkPanel, BorderLayout.LINE_START);
        button = new JButton("Display");
        button.addActionListener(this);
        gui.getContentPane().add(button, BorderLayout.LINE_END);
        ta = new TextArea();
        gui.getContentPane().add(ta, BorderLayout.SOUTH);
        gui.getContentPane().add(selection, BorderLayout.NORTH);
        JPanel boxPanel = new JPanel(new GridLayout(0, 1));
        sliceBox = createJComboBox();
        boxPanel.add(sliceBox);
        //TODO: Add membrane, Dense projections, adherens, and active zone boxes to the boxPanel
        gui.getContentPane().add(boxPanel, BorderLayout.CENTER);
        gui.pack();
        gui.setResizable(false);
	}
	
	public JComboBox createJComboBox()
	{
		String[] slices = new String[files.length];
		
		for (int i = 0; i < files.length; i++)
		{
			slices[i] = Integer.toString(firstSliceNum + i);
		}

		JComboBox comboBox = new JComboBox(slices);

		return comboBox;
	}
	
	public BranchGroup createSceneGraph(boolean dock, boolean tether, boolean normal, boolean large, boolean core, boolean dockedCore, boolean dense, boolean mito, boolean membrane, boolean slice) 
	{ 	
		//Create the root of the branch graph.
		// Add stuff to this to have it displayed
		BranchGroup objRoot = new BranchGroup(); 
		
		// If we're only displaying a single slice display just it
		if (slice)
		{
			Slice s = slices.get(selectedSlice);
			
			for (Vesicle v : s.vesicles)
			{
				Transform3D translate = new Transform3D();
				Vector3f vector = v.location; 
				translate.setTranslation(vector);
				TransformGroup objTranslate = new TransformGroup(translate);
				objRoot.addChild(objTranslate);
				objTranslate.addChild(new Sphere(v.radius, v.app));
			}
		}
		
		// Add vesicle groups to be displayed
		if (dock)
		{
			for (Vesicle d : docked)
			{
				Transform3D translate = new Transform3D();
				Vector3f vector = d.location; 
				translate.setTranslation(vector);
				TransformGroup objTranslate = new TransformGroup(translate);
				objRoot.addChild(objTranslate);
				objTranslate.addChild(new Sphere(d.radius, d.app));
			}
		}
		if (tether)
		{
			for (Vesicle t : tethered)
			{
				Transform3D translate = new Transform3D();
				Vector3f vector = t.location; 
				translate.setTranslation(vector);
				TransformGroup objTranslate = new TransformGroup(translate);
				objRoot.addChild(objTranslate);
				objTranslate.addChild(new Sphere(t.radius, t.app));
			}
		}
		if (normal)
		{
			for (Vesicle n : sv)
			{
				Transform3D translate = new Transform3D();
				Vector3f vector = n.location; 
				translate.setTranslation(vector);
				TransformGroup objTranslate = new TransformGroup(translate);
				objRoot.addChild(objTranslate);
				objTranslate.addChild(new Sphere(n.radius, n.app));
			}
		}
		if (large)
		{
			for (Vesicle l : larges)
			{
				Transform3D translate = new Transform3D();
				Vector3f vector = l.location; 
				translate.setTranslation(vector);
				TransformGroup objTranslate = new TransformGroup(translate);
				objRoot.addChild(objTranslate);
				objTranslate.addChild(new Sphere(l.radius, l.app));
			}
		}
		if (core)
		{
			for (Vesicle c : cores)
			{
				Transform3D translate = new Transform3D();
				Vector3f vector = c.location; 
				translate.setTranslation(vector);
				TransformGroup objTranslate = new TransformGroup(translate);
				objRoot.addChild(objTranslate);
				objTranslate.addChild(new Sphere(c.radius, c.app));
			}
		}
		if (dockedCore)
		{
			for (Vesicle dc : dockedCores)
			{
				Transform3D translate = new Transform3D();
				Vector3f vector = dc.location; 
				translate.setTranslation(vector);
				TransformGroup objTranslate = new TransformGroup(translate);
				objRoot.addChild(objTranslate);
				objTranslate.addChild(new Sphere(dc.radius, dc.app));
			}
		}
		if (dense)
		{
			Color3f denseColor = new Color3f(Color.BLACK);
			Color3f black = new Color3f(Color.BLACK);
			
			for(Projection p : projections)
			{
				PolygonAttributes myPA = new PolygonAttributes();
				TransparencyAttributes myTA = new TransparencyAttributes(TransparencyAttributes.NICEST, 0.5f);
				myPA.setBackFaceNormalFlip(true);
				myPA.setCullFace(PolygonAttributes.CULL_NONE);
				TriangleArray tA = new TriangleArray(p.trianglePoints.size(), GeometryArray.COORDINATES);
				tA.setCoordinates(0, p.triangles);
				GeometryInfo gi = new GeometryInfo(tA);
				NormalGenerator nG = new NormalGenerator();
				nG.setCreaseAngle(Math.PI);
				nG.generateNormals(gi);
				GeometryArray ga = gi.getGeometryArray();
				Material membMat = new Material(denseColor, black, denseColor, black, 1.0f);
				membMat.setLightingEnable(true);
				membMat.setColorTarget(Material.AMBIENT);
				Appearance myApp = new Appearance();
				ColoringAttributes ca = new ColoringAttributes();
				ca.setShadeModel(ColoringAttributes.SHADE_GOURAUD);
				myApp.setColoringAttributes(ca);
				myApp.setMaterial(membMat);
				myApp.setPolygonAttributes(myPA);  // Prevent see through
				myApp.setTransparencyAttributes(myTA);
				Shape3D myShape = new Shape3D(ga, myApp);
				objRoot.addChild(myShape);
			}
		}
		if (mito)
		{
			Color3f mitoColor = new Color3f(Color.BLUE);
			Color3f black = new Color3f(Color.BLACK);
			
			for(MitoData md : mitoData)
			{
				PolygonAttributes myPA = new PolygonAttributes();
				TransparencyAttributes myTA = new TransparencyAttributes(TransparencyAttributes.NICEST, 0.5f);
				myPA.setBackFaceNormalFlip(true);
				myPA.setCullFace(PolygonAttributes.CULL_NONE);
				TriangleArray tA = new TriangleArray(md.trianglePoints.size(), GeometryArray.COORDINATES);
				tA.setCoordinates(0, md.triangles);
				GeometryInfo gi = new GeometryInfo(tA);
				NormalGenerator nG = new NormalGenerator();
				nG.setCreaseAngle(Math.PI);
				nG.generateNormals(gi);
				GeometryArray ga = gi.getGeometryArray();
				Material membMat = new Material(mitoColor, black, mitoColor, black, 1.0f);
				membMat.setLightingEnable(true);
				membMat.setColorTarget(Material.AMBIENT);
				Appearance myApp = new Appearance();
				ColoringAttributes ca = new ColoringAttributes();
				ca.setShadeModel(ColoringAttributes.SHADE_GOURAUD);
				myApp.setColoringAttributes(ca);
				myApp.setMaterial(membMat);
				myApp.setPolygonAttributes(myPA);  // Prevent see through
				myApp.setTransparencyAttributes(myTA);
				Shape3D myShape = new Shape3D(ga, myApp);
				objRoot.addChild(myShape);
			}
		}
		if (membrane && !(files.length == 1))
		{
			//TODO Membrane issues
			Color3f membColor = new Color3f(Color.RED);
			Color3f black = new Color3f(Color.BLACK);
			//ShaderAppearance sApp = null;
			//ShaderProgram gouraudSP = null;
			//ShaderProgram phongSP = null;
			
			/*sApp = new ShaderAppearance();
			sApp.setCapability(ShaderAppearance.ALLOW_SHADER_PROGRAM_WRITE);
			Material m = new Material(membColor, black, membColor, black,
					100.0f);
			sApp.setMaterial(m);
			
			// Create Gouraud and Phong shader programs
			String vertexProgram = null;
			String fragmentProgram = null;
			Shader[] shaders = new Shader[2];
			
			try 
			{
				vertexProgram = StringIO.readFully(Resources.getResource("glsl_shader/gouraud.vert"));
				fragmentProgram = StringIO.readFully(Resources.getResource("glsl_shader/gouraud.frag"));
			} 
			catch (IOException e) 
			{
				throw new RuntimeException(e);
			}

			shaders[0] = new SourceCodeShader(Shader.SHADING_LANGUAGE_GLSL,
					Shader.SHADER_TYPE_VERTEX, vertexProgram);
			shaders[1] = new SourceCodeShader(Shader.SHADING_LANGUAGE_GLSL,
					Shader.SHADER_TYPE_FRAGMENT, fragmentProgram);
			gouraudSP = new GLSLShaderProgram();
			gouraudSP.setShaders(shaders);

			try 
			{
				vertexProgram = StringIO.readFully(Resources.getResource("glsl_shader/phong.vert"));
				fragmentProgram = StringIO.readFully(Resources.getResource("glsl_shader/phong.frag"));
			} 
			catch (IOException e) 
			{
				throw new RuntimeException(e);
			}
			shaders[0] = new SourceCodeShader(Shader.SHADING_LANGUAGE_GLSL,
					Shader.SHADER_TYPE_VERTEX, vertexProgram);
			shaders[1] = new SourceCodeShader(Shader.SHADING_LANGUAGE_GLSL,
					Shader.SHADER_TYPE_FRAGMENT, fragmentProgram);
			phongSP = new GLSLShaderProgram();
			phongSP.setShaders(shaders);
			
			//sApp.setShaderProgram(gouraudSP);
			sApp.setShaderProgram(phongSP);
			
			GeometryInfo gi = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY);
			gi.setCoordinates(points);
			NormalGenerator ng = new NormalGenerator();
			ng.setCreaseAngle(Math.PI);
			ng.generateNormals(gi);
			GeometryArray ga = gi.getGeometryArray();
			
			Shape3D myShape = new Shape3D(ga, sApp);
			objRoot.addChild(myShape);*/
			
				/*Color3f membColor = new Color3f(Color.RED);
				Color3f black = new Color3f(Color.BLUE);
				GeometryInfo gi = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY);
				gi.setCoordinates(points);
				NormalGenerator ng = new NormalGenerator();
				ng.setCreaseAngle(Math.PI);
				ng.generateNormals(gi);
				GeometryArray ga = gi.getGeometryArray();
				PolygonAttributes myPA = new PolygonAttributes();
				//myPA.setCullFace(PolygonAttributes.CULL_NONE);
				myPA.setBackFaceNormalFlip(true);*/
					PolygonAttributes myPA = new PolygonAttributes();
					TransparencyAttributes myTA = new TransparencyAttributes(TransparencyAttributes.NICEST, 0.5f);
					myPA.setBackFaceNormalFlip(true);
					myPA.setCullFace(PolygonAttributes.CULL_NONE);
					TriangleArray tA = new TriangleArray(tPoints.size(), GeometryArray.COORDINATES);
					tA.setCoordinates(0, points);
					GeometryInfo gi = new GeometryInfo(tA);
					NormalGenerator nG = new NormalGenerator();
					nG.setCreaseAngle(Math.PI);
					nG.generateNormals(gi);
					GeometryArray ga = gi.getGeometryArray();
					Material membMat = new Material(membColor, black, membColor, black, 1.0f);
					membMat.setLightingEnable(true);
					membMat.setColorTarget(Material.AMBIENT);
					Appearance myApp = new Appearance();
					ColoringAttributes ca = new ColoringAttributes();
					ca.setShadeModel(ColoringAttributes.SHADE_GOURAUD);
					myApp.setColoringAttributes(ca);
					myApp.setMaterial(membMat);
					myApp.setPolygonAttributes(myPA);  // Prevent see through
					myApp.setTransparencyAttributes(myTA);
					Shape3D myShape = new Shape3D(ga, myApp);
				/*Appearance myApp = new Appearance();
				ColoringAttributes ca = new ColoringAttributes();
				ca.setShadeModel(ColoringAttributes.SHADE_GOURAUD);
				Material membMat = new Material(membColor, black, membColor, black, 1.0f);
				membMat.setLightingEnable(true);
				membMat.setColorTarget(Material.AMBIENT);
				myApp.setPolygonAttributes(myPA);
				myApp.setMaterial(membMat);
				Shape3D myShape = new Shape3D(ga, myApp);*/
				
				// Clip the membrane so it's not a sphere
				ModelClip cPlane = new ModelClip();
				boolean enables[] = {false, false, false, false, false, false};
				cPlane.setEnables(enables);
				Vector4d top = new Vector4d(0.0, 0.0, 1.0, -maxZ + 3);
				Vector4d bottom = new Vector4d(0.0, 0.0, -1.0, minZ + 3);
				cPlane.setPlane(1, top);
				cPlane.setEnable(1, true);
				cPlane.setPlane(2, bottom);
				cPlane.setEnable(2, true);
				Group shape = new Group();
				shape.addChild(myShape);
				cPlane.addScope(shape);
				cPlane.setInfluencingBounds(objRoot.getBounds());
				
				Group g = new Group();
				g.addChild(cPlane);
				g.addChild(shape);
				
				objRoot.addChild(g);
		}
		
		
		BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 5000.0);
		// Set up the lighting
		
		Color3f light1Color = new Color3f(1f, 1f, 1f);
	    
	    Vector3f light1Direction = new Vector3f(4.0f, -7.0f, -12.0f);
	    DirectionalLight light1 = new DirectionalLight(light1Color, light1Direction);
	    light1.setInfluencingBounds(bounds);
	    objRoot.addChild(light1);

	    Vector3f light2Direction = new Vector3f();
	    light1Direction.negate();
	    light2Direction = light1Direction;
	    DirectionalLight light2 = new DirectionalLight(light1Color, light2Direction);
	    light2.setInfluencingBounds(bounds);
	    objRoot.addChild(light2);
	    
	    
	    Vector3f light3Direction = new Vector3f(0.0f, -1.0f, 0.0f);
	    DirectionalLight light3 = new DirectionalLight(light1Color, light3Direction);
	    light3.setInfluencingBounds(bounds);
	    objRoot.addChild(light3);
	  
			
		return objRoot;
	}
	
	public static void parse(File file)
	{
		int id = 0;
		String name = "";
		float x;
		float y;
		float rad;
		String xValues;
		String yValues;
		Slice newSlice = new Slice();
		
		try
		{
			Scanner s = new Scanner(file);
			
			while (s.hasNext())
			{
				id = s.nextInt();
				
				if (id == 1)
				{
					name = s.next();
					x = s.nextFloat();
					y = s.nextFloat();
					rad = s.nextFloat();
					rad *= magnification;
					offsetX += x;
					offsetY += y;
					vesicleCount ++;
					
					if (name.equals("docked_SV"))
					{
						Vesicle dock = new Docked(x, y, rad, sliceNumber, sliceWidth);
						docked.add(dock);
						newSlice.addVesicle(dock);
					}
					else if (name.equals("tethered_SV"))
					{
						Vesicle teth = new Tethered(x, y, rad, sliceNumber, sliceWidth);
						tethered.add(teth);
						newSlice.addVesicle(teth);
					}
					else if (name.equals("SV"))
					{
						Vesicle norm = new Normal(x, y, rad, sliceNumber, sliceWidth);
						sv.add(norm);
						newSlice.addVesicle(norm);
					}
					else if (name.equals("LV"))
					{
						Vesicle large = new Large(x, y, rad, sliceNumber, sliceWidth);
						larges.add(large);
						newSlice.addVesicle(large);
					}
					else if (name.equals("DCV"))
					{
						Vesicle core = new Core(x, y, rad, sliceNumber, sliceWidth);
						cores.add(core);
						newSlice.addVesicle(core);
					}
					else if (name.equals("docked_DCV"))
					{
						Vesicle dockedCore = new Core(x, y, rad, sliceNumber, sliceWidth);
						dockedCores.add(dockedCore);
						newSlice.addVesicle(dockedCore);
					}
					else if (name.equals("SV_kinesin"))
					{
						
					}
					else if (name.equals("SV_dynein"))
					{
						
					}
					//else if (name.equals(microtubules_x))
					//{
						//TODO: Microtubules not implemented
					//}
					else if (name.equals("black_tubules"))
					{
						
					}
				}
				else if (id == 3)
				{
					// Get the String identifier
					name = s.next();
					// Discard the volume number
					s.nextInt();
					
					xValues = s.next();
					yValues = s.next();
					String newX = xValues.replace(',', ' ');
					String newY = yValues.replace(',', ' ');
					
					if (name.equals("Dense_projection"))
					{
						// IMPORTANT NOTE: If there can be multiple dense in a single slice use an int++ and pass that to Dense as well for an identifier.  reset the int for each slice.
						Dense d = new Dense(newX, newY, sliceNumber, sliceWidth);
						densePoints.add(d);
					}
					else if (name.equals("Plasma_Membrane"))
					{
						Membrane m = new Membrane(newX, newY, sliceNumber, sliceWidth);
						membranePoints.add(m);
					}
					else if (name.equals("Endosome"))
					{
						//TODO: Endosomes not yet implemented
					}
					else if (name.equals("Mitochondria"))
					{
						Mitochondria m = new Mitochondria(newX, newY, sliceNumber, sliceWidth);
						mitochondriaPoints.add(m);
					}
				}
				else if (id == 7)
				{
					// TODO: Adherens Junctions not yet implemented
					s.next();
					s.next();
					s.next();
				}
			}
			
			slices.add(newSlice);
		}
		catch (FileNotFoundException e)
		{
			System.out.println("File not found.");
		}
	}
	
	public ArrayList<Point3f> createTriangles(File file2)
	{
		ArrayList<Point3f> points = new ArrayList<Point3f>();
		Point3f point = new Point3f();
		String tempSX = "";
		int tempX = 0;
		String tempSY = "";
		int tempY = 0;
		String tempSZ = "";
		int tempZ = 0;
		
		try
		{
			Scanner s = new Scanner(file2);
			s.nextLine();
			s.nextLine();
			
			while (s.hasNext())
			{
				tempSX = s.next();
				tempX = checkValue(tempSX);
				tempSY = s.next();
				tempY = checkValue(tempSY);
				tempSZ = s.next();
				tempZ = checkValue(tempSZ);
				point = new Point3f(tempX, tempY, tempZ);
				points.add(point);
				
				tempSX = s.next();
				tempX = checkValue(tempSX);
				tempSY = s.next();
				tempY = checkValue(tempSY);
				tempSZ = s.next();
				tempZ = checkValue(tempSZ);				
				point = new Point3f(tempX, tempY, tempZ);
				points.add(point);
				
				tempSX = s.next();
				tempX = checkValue(tempSX);
				tempSY = s.next();
				tempY = checkValue(tempSY);				
				tempSZ = s.next();
				tempZ = checkValue(tempSZ);				
				point = new Point3f(tempX, tempY, tempZ);
				points.add(point);
				
				// Triple skip, due to space after last Z
				s.nextLine();
				s.nextLine();
				s.nextLine();
			}
		}
		catch (FileNotFoundException e)
		{
			System.out.println("File not found.");
		}
		
		return points;
	}
	
	/*
	 *   Qhull sometimes feeds back values of -x.xxxexx.  Just set these to 0.
	 */
	public int checkValue(String s)
	{
		if (s.lastIndexOf("e") != -1)
			return 0;
		else
			return Integer.parseInt(s);
	}

	public void actionPerformed(ActionEvent e) 
	{
		// Clear the 3D tree each time the button is clicked to erase anything not
		// selected this time that was previously.
		scene.detach();
		boolean showDocked = false;
		boolean showTethered = false;
		boolean showNorm = false;
		boolean showLarge = false;
		boolean showCore = false;
		boolean showDockedCore = false;
		boolean showDense = false;
		boolean showMito = false;
		boolean showMemb = false;
		boolean singleSlice = false;
		int dockCount = 0;
		int tetherCount = 0;
		int normCount = 0;
		int largeCount = 0;
		int coreCount = 0;
		int dockedCoreCount = 0;
		ta.setText("");
		
		if (all.isSelected())
		{
			// Add to the 3D tree if the box is checked.
			if (dock.isSelected())
			{
				showDocked = true;
				ta.append("Displaying all " + docked.size() +" docked vesicles.\n");
			}
			if (tether.isSelected())
			{
				showTethered = true;
				ta.append("Displaying all " + tethered.size() +" tethered vesicles.\n");
			}
			if (norm.isSelected())
			{
				showNorm = true;
				ta.append("Displaying all " + sv.size() +" synaptic vesicles.\n");
			}
			if (lv.isSelected())
			{
				showLarge = true;
				ta.append("Displaying all " + larges.size() +" large vesicles.\n");
			}
			if (dcv.isSelected())
			{
				showCore = true;
				ta.append("Displaying all " + cores.size() +" dense core vesicles.\n");
			}
			if (ddcv.isSelected())
			{
				showDockedCore = true;
				ta.append("Displaying all " + dockedCores.size() +" docked dense core vesicles.\n");
			}
			if (dp.isSelected())
			{
				showDense = true;
				if (projections.size() == 1)
					ta.append("Displaying the only dense projection.\n");
				else if (mitoData.size() > 1)
					ta.append("Displaying all " + projections.size() +" dense projections.\n");
			}
			if (mito.isSelected())
			{
				showMito = true;
				if (mitoData.size() == 1)
					ta.append("Displaying the only mitochondria.\n");
				else if (mitoData.size() > 1)
					ta.append("Displaying all " + mitoData.size() +" mitochondria.\n");
			}
			if (memb.isSelected())
			{
				showMemb = true;
				ta.append("Displaying the membrane.");
			}
		}
		else
		{
			singleSlice = true;
			selectedSlice = sliceBox.getSelectedIndex();
			ArrayList<Vesicle> ves = slices.get(selectedSlice).getVesicleList();
			
			for (Vesicle v : ves)
			{
				if (v.name.equals("Docked"))
					dockCount++;
				if (v.name.equals("Tethered"))
					tetherCount++;
				if (v.name.equals("Normal"))
					normCount++;
				if (v.name.equals("Large"))
					largeCount++;
				if (v.name.equals("Core"))
					coreCount++;
				if (v.name.equals("DockedCore"))
					dockedCoreCount++;
			}
			
			ta.append("This slice has:\n");
			if (dockCount == 1)             ta.append("1 docked vesicle.\n");
			else if (dockCount != 0)        ta.append(dockCount + " docked vesicles.\n");
			if (tetherCount == 1)           ta.append("1 tethered vesicle.\n");
			else if (tetherCount != 0)      ta.append(tetherCount + " tethered vesicles.\n");
			if (normCount == 1)             ta.append("1 synaptic vesicle.\n");
			else if (normCount != 0)        ta.append(normCount + " synaptic vesicles.\n");
			if (largeCount == 1)            ta.append("1 large vesicle.\n");
			else if (largeCount != 0)       ta.append(largeCount + " large vesicles.\n");
			if (coreCount == 1)             ta.append("1 dense core vesicle.\n");
			else if (coreCount != 0)        ta.append(coreCount + " dense core vesicles.\n");
			if (dockedCoreCount == 1)       ta.append("1 docked dense core vesicle.\n");
			else if (dockedCoreCount != 0)  ta.append(coreCount + " docked dense core vesicles.\n");
		}
		
		// Call CreateSceneGraph with the proper booleans to create the new image
		try
		{
			scene = createSceneGraph(showDocked, showTethered, showNorm, showLarge, showCore, showDockedCore, showDense, showMito, showMemb, singleSlice);
		}
		// Weird error popping randomly?, doesn't break code, just requires button to be clicked again.
		catch(java.lang.IllegalArgumentException error)
		{
			ta.setText("");
			ta.append("Sorry, please click the button again.");
		}
		scene.setCapability(BranchGroup.ALLOW_DETACH);
		scene.compile();
		
		// Attach the new scene to simpleU
		simpleU.addBranchGraph(scene);
	}
	
	public void createMembrane()
	{
		double tempMaxZ = 0.0;
		double tempMinZ = 0.0;
		
		try
		{
			PrintWriter q = new PrintWriter(new FileOutputStream("membrane.txt"), true);
			
			q.println(3);
			q.println(total);
			
			for (Membrane m : membranePoints)
			{
				for (int i = 0; i < m.coordinates.size(); i++)
				{
					int[] current = m.coordinates.get(i);
					
					for (int j = 0; j < 3; j++)
					{
						if (j == 2) // Z values
						{
							q.println(current[j]);
							if (current[j] > tempMaxZ)
								tempMaxZ = current[j];
							if (current[j] < tempMinZ)
								tempMinZ = current[j];
						}
						else if (j == 1) // Y values
							q.print((float)current[j] + " ");
						else // X values
							q.print((float)current[j] + " ");
					}
				}
			}
		}
		catch (FileNotFoundException e)
		{
			System.out.println("File not found!");
		}
		
		maxZ = tempMaxZ;
		minZ = tempMinZ;
		
		// Create the membrane assuming we have more than one file
		if (!(files.length == 1))
		{
			// Run Script
			try
			{
				Runtime rt = Runtime.getRuntime();
				Process proc = rt.exec("./sample.sh");
				proc.waitFor();
			}
			catch (Exception e)
			{
				System.out.println("Could not find script file.");
			}
			
			// Use the file created by qhull
			File file = new File("triangles.txt");
	
			file.deleteOnExit();
			tPoints = createTriangles(file);
			int filler = tPoints.size();
			points = new Point3f[filler];
			tPoints.toArray(points);
		}
	}
	
	public void createProjection(Projection p)
	{
		int denseTotal = 0;
		ArrayList<Point3f> points = new ArrayList<Point3f>();
		Point3f[] pointArray;
		
		for (Dense d : p.denseList)
		{
			denseTotal += d.coordinates.size();
		}
		
		try
		{
			PrintWriter q = new PrintWriter(new FileOutputStream("dense.txt"), true);
			
			q.println(3);
			q.println(denseTotal);
			
			for (Dense d : p.denseList)
			{
				for (int i = 0; i < d.coordinates.size(); i++)
				{
					int[] current = d.coordinates.get(i);
					
					for (int j = 0; j < 3; j++)
					{
						if (j == 2)
							q.println(current[j]);
						else
							q.print((float)current[j] + " ");
					}
				}
			}
		}
		catch (FileNotFoundException e)
		{
			System.out.println("File not found!");
		}
		
		try
		{
			Runtime rt = Runtime.getRuntime();
			Process proc = rt.exec("./dense.sh");
			proc.waitFor();
		}
		catch (Exception e)
		{
			System.out.println("Could not find dense script file.");
		}
		
		// Use the file created by qhull
		File file = new File("denseTri.txt");

		file.deleteOnExit();
		points = createTriangles(file);
		int filler = points.size();
		pointArray = new Point3f[filler];
		points.toArray(pointArray);
		p.setPoints(points, pointArray);
	}
	
	public void createMito(MitoData md)
	{
		int mitoTotal = 0;
		ArrayList<Point3f> points = new ArrayList<Point3f>();
		Point3f[] pointArray;
		
		for (Mitochondria m : md.mitoList)
		{
			mitoTotal += m.coordinates.size();
		}
		
		try
		{
			PrintWriter q = new PrintWriter(new FileOutputStream("mito.txt"), true);
			
			q.println(3);
			q.println(mitoTotal);
			
			for (Mitochondria m : md.mitoList)
			{
				for (int i = 0; i < m.coordinates.size(); i++)
				{
					int[] current = m.coordinates.get(i);
					
					for (int j = 0; j < 3; j++)
					{
						if (j == 2)
							q.println(current[j]);
						else
							q.print((float)current[j] + " ");
					}
				}
			}
		}
		catch (FileNotFoundException e)
		{
			System.out.println("File not found!");
		}
		
		try
		{
			Runtime rt = Runtime.getRuntime();
			Process proc = rt.exec("./mito.sh");
			proc.waitFor();
		}
		catch (Exception e)
		{
			System.out.println("Could not find mito script file.");
		}
		
		// Use the file created by qhull
		File file = new File("mitoTri.txt");

		file.deleteOnExit();
		points = createTriangles(file);
		int filler = points.size();
		pointArray = new Point3f[filler];
		points.toArray(pointArray);
		md.setPoints(points, pointArray);
	}
	
	public BranchGroup createBackground() 
	{
	    // create a parent BranchGroup for the Background
	    BranchGroup backgroundGroup = new BranchGroup();

	    // create a new Background node
	    Background back = new Background(new Color3f(Color.WHITE));

	    // set the range of influence of the background
	    back.setApplicationBounds(new BoundingSphere(new Point3d(0.0, 0.0, 0.0),30000.0));

	    // create a BranchGroup that will hold
	    // our Sphere geometry
	    BranchGroup bgGeometry = new BranchGroup();

	    // create an appearance for the Sphere
	    Appearance app = new Appearance();

	    // create the Sphere geometry with radius 1.0
	    // we tell the Sphere to generate texture coordinates
	    // to enable the texture image to be rendered
	    // and because we are *inside* the Sphere we have to generate
	    // Normal coordinates inwards or the Sphere will not be visible.
	    Sphere sphere = new Sphere(1.0f, Primitive.GENERATE_TEXTURE_COORDS
	        | Primitive.GENERATE_NORMALS_INWARD, app);

	    // start wiring everything together
	    // add the Sphere to its parent BranchGroup
	    bgGeometry.addChild(sphere);

	    // assign the BranchGroup to the Background as geometry.
	    back.setGeometry(bgGeometry);

	    // add the Background node to its parent BranchGroup
	    backgroundGroup.addChild(back);

	    return backgroundGroup;
	}
	
	public void centerPoints()
	{
		// Calculate offsets to center all points
		offsetX /= vesicleCount;
		offsetY /= vesicleCount;
		offsetZ = (files.length / 2) * sliceWidth;
		
		// Center Membrane Points
		// Calculate the total number of points in the membrane.
		for (Membrane m : membranePoints)
		{
			total += m.coordinates.size();
			
			for (int i = 0; i < m.coordinates.size(); i++)
			{
				m.coordinates.get(i)[0] -= offsetX;
				m.coordinates.get(i)[1] -= offsetY;
				m.coordinates.get(i)[2] -= offsetZ;
			}
		}
		
		// Center Dense Projection points
		for (Dense d : densePoints)
		{
			for (int i = 0; i < d.coordinates.size(); i++)
			{
				d.coordinates.get(i)[0] -= offsetX;
				d.coordinates.get(i)[1] -= offsetY;
				d.coordinates.get(i)[2] -= offsetZ;
			}
		}
		
		// Center Mitochondria points
		for (Mitochondria m : mitochondriaPoints)
		{
			for (int i = 0; i < m.coordinates.size(); i++)
			{
				m.coordinates.get(i)[0] -= offsetX;
				m.coordinates.get(i)[1] -= offsetY;
				m.coordinates.get(i)[2] -= offsetZ;
			}
		}
		
		// Move the vesicles to the origin
		for (Vesicle d : docked)
		{
			d.location.x -= offsetX;
			d.location.y -= offsetY;
			d.location.z -= offsetZ;
		}

		for (Vesicle t : tethered)
		{
			t.location.x -= offsetX;
			t.location.y -= offsetY;
			t.location.z -= offsetZ;
		}

		for (Vesicle n : sv)
		{
			n.location.x -= offsetX;
			n.location.y -= offsetY;
			n.location.z -= offsetZ;
		}

		for (Vesicle l : larges)
		{
			l.location.x -= offsetX;
			l.location.y -= offsetY;
			l.location.z -= offsetZ;
		}

		for (Vesicle c : cores)
		{
			c.location.x -= offsetX;
			c.location.y -= offsetY;
			c.location.z -= offsetZ;
		}
		for (Vesicle dc : dockedCores)
		{
			dc.location.x -= offsetX;
			dc.location.y -= offsetY;
			dc.location.z -= offsetZ;
		}
	}
	
	public void separateDense()
	{
		int checker = 0;
		int listPos = 0;
		Projection p = new Projection();
		projections.add(p);
		
		for (Dense d : densePoints)
		{
			if (checker == 0)
			{
				checker = d.sliceNumber;
				projections.get(listPos).denseList.add(d);
			}
			else if (checker == d.sliceNumber - 1)
			{
				checker = d.sliceNumber;
				projections.get(listPos).denseList.add(d);
			}
			else
			{
				checker = 0;
				listPos++;
				p = new Projection();
				projections.add(p);
				projections.get(listPos).denseList.add(d);
			}
		}
	}
	
	public void separateMito()
	{
		int checker = 0;
		int listPos = 0;
		MitoData md = new MitoData();
		mitoData.add(md);
		
		for (Mitochondria m : mitochondriaPoints)
		{
			if (checker == 0)
			{
				checker = m.sliceNumber;
				mitoData.get(listPos).mitoList.add(m);
			}
			else if (checker == m.sliceNumber - 1)
			{
				checker = m.sliceNumber;
				mitoData.get(listPos).mitoList.add(m);
			}
			else
			{
				checker = 0;
				listPos++;
				md = new MitoData();
				mitoData.add(md);
				mitoData.get(listPos).mitoList.add(m);
			}
		}
	}

	public void mouseWheelMoved(MouseWheelEvent e) 
	{
		if (slice.isSelected())
		{
			int initial = sliceBox.getSelectedIndex();
			int notches = -e.getWheelRotation();
			
			if (notches > 0 && !(initial + notches > files.length - 1))
			{
				sliceBox.setSelectedIndex(initial + notches);
				button.doClick();
			}
			else if (notches < 0 && !(initial + notches < 0))
			{
				sliceBox.setSelectedIndex(initial + notches);
				button.doClick();
			}
		}
	}
}
